using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

using Atomic.Libraries.Plotting.SpacePlotting;

namespace Atomic.Libraries.Mathematics
{
	/// <summary>
	/// Represents a vector in the 3-dimensional space.
	/// </summary>
	[Serializable]
	[DebuggerStepThrough]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public sealed class SpaceVector : ISpacePoint
	{
		private Vector inner;

		static SpaceVector()
		{
			Zero = new SpaceVector(0.0, 0.0, 0.0);
		}

		public SpaceVector(double x, double y, double z)
			: this(new double[] { x, y, z })
		{
		}

		public SpaceVector(double[] entries)
			: this(new Vector(entries))
		{
			if (entries.Length != 3)
			{
				throw new ArgumentException("Invalid size.");
			}
		}

		private SpaceVector(Vector inner)
		{
			this.inner = inner;
		}

		public override string ToString()
		{
			//return string.Format(CultureInfo.InvariantCulture, "({0:f4}, {1:f4}, {2:f4})", X, Y, Z);
			return inner.ToString();
		}

		public bool Equals(SpaceVector other)
		{
			if (object.ReferenceEquals(other, null))
			{
				return false;
			}

			return inner == other.inner;
		}

		public override bool Equals(object other)
		{
			return Equals(other as SpaceVector);
		}

		public override int GetHashCode()
		{
			return inner.GetHashCode();
		}

		public static double Dot(SpaceVector a, SpaceVector b)
		{
			return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
		}

		public static SpaceVector Cross(SpaceVector a, SpaceVector b)
		{
			return new SpaceVector(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X);
		}

		public static double Norm(SpaceVector a)
		{
			return Vector.Norm(a.inner);
		}

		public static SpaceVector UnitVector(SpaceVector a)
		{
			return a / Norm(a);
		}

		public static SpaceVector operator +(SpaceVector a, SpaceVector b)
		{
			return new SpaceVector(a.inner + b.inner);
		}

		public static SpaceVector operator +(SpaceVector a, double t)
		{
			return new SpaceVector(a.inner + t);
		}

		public static SpaceVector operator +(double t, SpaceVector a)
		{
			return new SpaceVector(t + a.inner);
		}

		public static SpaceVector operator -(SpaceVector a)
		{
			return new SpaceVector(-a.inner);
		}

		public static SpaceVector operator -(SpaceVector a, SpaceVector b)
		{
			return new SpaceVector(a.inner - b.inner);
		}

		public static SpaceVector operator -(SpaceVector a, double t)
		{
			return new SpaceVector(a.inner - t);
		}

		public static SpaceVector operator -(double t, SpaceVector a)
		{
			return new SpaceVector(t - a.inner);
		}

		public static SpaceVector operator *(SpaceMatrix a, SpaceVector b)
		{
			return new SpaceVector((Matrix)a * b.inner);
		}

		public static SpaceVector operator *(SpaceVector a, double t)
		{
			return new SpaceVector(a.inner * t);
		}

		public static SpaceVector operator *(double t, SpaceVector a)
		{
			return new SpaceVector(t * a.inner);
		}

		public static SpaceVector operator /(SpaceVector a, double t)
		{
			return new SpaceVector(a.inner / t);
		}

		public static bool operator ==(SpaceVector a, SpaceVector b)
		{
			if (object.ReferenceEquals(a, null))
			{
				if (object.ReferenceEquals(b, null))
				{
					return true;
				}

				return false;
			}

			return a.Equals(b);
		}

		public static bool operator !=(SpaceVector a, SpaceVector b)
		{
			return !(a == b);
		}

		public static implicit operator Vector(SpaceVector a)
		{
			return a.inner;
		}

		public static explicit operator SpaceVector(Vector a)
		{
			if (a.Length != 3)
			{
				throw new InvalidCastException();
			}

			return new SpaceVector(a);
		}

		public static SpaceVector Zero
		{
			get;
			private set;
		}

		public double this[int index]
		{
			get
			{
				return inner[index];
			}
		}

		public double X
		{
			get
			{
				return inner[0];
			}
		}

		public double Y
		{
			get
			{
				return inner[1];
			}
		}

		public double Z
		{
			get
			{
				return inner[2];
			}
		}
		
		private string DebuggerDisplay
		{
			get
			{
				return ToString();
			}
		}
	}
}
